package com.zjl.media.netty;

import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.zjl.media.config.properties.MediaProperties;
import com.zjl.media.core.MediaManager;
import com.zjl.media.netty.dto.VideoParam;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * @author linfuxin Created on 2022-03-01 15:50:06
 */
@ChannelHandler.Sharable
@Slf4j
public class FlvHandler extends SimpleChannelInboundHandler<Object> {

    private WebSocketServerHandshaker handshake;

    private final MediaManager mediaManager;
    private final RSA rsa;

    public FlvHandler(MediaManager mediaManager, MediaProperties mediaProperties) {
        this.mediaManager = mediaManager;
        this.rsa = new RSA(mediaProperties.getParam().getPrivateKey(), null);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            handlerFullHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketRequest(ctx, (WebSocketFrame) msg);
        }
    }

    private void handlerFullHttpRequest(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) {
        final String mediaUrl = "/live";
        if (!mediaUrl.equals(new QueryStringDecoder(fullHttpRequest.uri()).path())) {
            sendError(ctx, HttpResponseStatus.BAD_REQUEST);
            return;
        }

        WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(
                getWebSocketLocation(fullHttpRequest), null, true, 5 * 1024 * 1024);
        handshake = factory.newHandshaker(fullHttpRequest);
        if (handshake == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
            return;
        }
        HttpResponse rsp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        rsp.headers().set(HttpHeaderNames.SERVER, "zjl-media");
        DefaultChannelPromise channelPromise = new DefaultChannelPromise(ctx.channel());
        handshake.handshake(ctx.channel(), fullHttpRequest, rsp.headers(), channelPromise);

        VideoParam videoParam = resolveParam(fullHttpRequest.uri());
        if (videoParam == null || videoParam.getRtsp() == null) {
            sendError(ctx, HttpResponseStatus.BAD_REQUEST);
            return;
        }
        mediaManager.push(videoParam, ctx);
    }

    private VideoParam resolveParam(String uri) {
        try {
            String param = uri.substring(uri.indexOf("?") + 1);
            String decryptParam = rsa.decryptStr(param, KeyType.PrivateKey);
            String[] args = decryptParam.split("&");
            VideoParam videoParam = new VideoParam();
            for (String argument : args) {
                String[] kv = argument.split("=");
                String key = kv[0];
                String value = kv[1];
                if ("rtsp".equals(key)) {
                    videoParam.setRtsp(value);
                } else if ("hardDecode".equals(key)) {
                    videoParam.setHardDecode(Boolean.parseBoolean(value));
                } else if ("javacv".equals(key)) {
                    videoParam.setJavacv(Boolean.parseBoolean(value));
                }
            }
            return videoParam;
        } catch (RuntimeException e) {
            return null;
        }
    }

    private void handleWebSocketRequest(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 握手PING/PONG
        if (frame instanceof PingWebSocketFrame) {
            ctx.write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }

        // 关闭
        if (frame instanceof CloseWebSocketFrame) {
            // todo handshake 并发?
            handshake.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
    }

    /**
     * ws握手地址
     */
    private String getWebSocketLocation(FullHttpRequest request) {
        String location = request.headers().get(HttpHeaderNames.HOST) + request.uri();
        return "ws://" + location;
    }

    /**
     * 错误请求响应
     *
     * @param ctx ctx
     * @param status status
     */
    private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
                Unpooled.copiedBuffer("请求地址有误: " + status + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");

        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    public static void main(String[] args) {
//        RSA test = new RSA();
//        System.out.println(test.getPrivateKeyBase64());
//        System.out.println(test.getPublicKeyBase64());
        String privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANYamDkRasOMonoNSXhh4nhNQEP6kyNl9kHssfNrFAvcDEs4lQSDqEWCvFBsenjb0wK0wnUJ+qo3BFfCSHZ8tny57ZZtF4ol0aqWwEH1KiHgLJEY22ZHNXxXoTakQdUh8a9aXH1/pdZDTfCQ78uhtMxnn18MNytxaKtaaZG6ErdjAgMBAAECgYBREzcLfTXxoOKx1zA4HtD0f0RTuaJYKDctVaU5KRYpO4WIQn/jeruQvuXyinGCLLPl9W8hhMrgA/9oABESlT1AoomV8wvg7rP6FI7WNioYT4xgY4wDchOvLPkD79vGFp5m9d8kohfuLCdLmtyEN1nwQ06IKAdzjwlbU7Z6V3vKiQJBAO9CkN4YRybGq3wk0Y5mR+sPtWEbyg09w/hHRZXcu9hqJvhZ1xQUvKFRe/ftECuLuLTgtIWDm5xvDTcmQBvb3j0CQQDlFXO+CaEGxSxNDQaIiOyfNVkSl9AnXhFuB0DhAnkFe7cr0mjYLuy32adGs7RQdKcATIUtoYt+kBtgrFcoaOYfAkBUvsIrOHQzQtlIro3fH1FH7Bd1nu8xQ3hfP3dhPn72h5roCoJI5ZNunDsJtNkuY134C0NFqzj57sfCifTZKUaNAkA73DgrT07RexJRa/bqFIVoMMN+tUbCErWRP7n3UfMXuLUrcUbOPqzCedBZDDowz9qXnGcsfkr6HmPZBg+cUpTXAkBs2KgYEjirwtoYy2KaeYZkvdbDjMM1hGUtFxz0vAHXF6azjigLKlQi3NxH+A4pDzqcFEaIzNPNHqPYiqJtm0/u";
        String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDWGpg5EWrDjKJ6DUl4YeJ4TUBD+pMjZfZB7LHzaxQL3AxLOJUEg6hFgrxQbHp429MCtMJ1CfqqNwRXwkh2fLZ8ue2WbReKJdGqlsBB9Soh4CyRGNtmRzV8V6E2pEHVIfGvWlx9f6XWQ03wkO/LobTMZ59fDDcrcWirWmmRuhK3YwIDAQAB";
        RSA publicRsa = new RSA(null, publicKey);
        String rawText = publicRsa.encryptBase64("123456", KeyType.PublicKey);

        RSA privateRsa = new RSA(privateKey, null);
        String plainText = privateRsa.decryptStr(rawText, KeyType.PrivateKey);
        System.out.println(plainText);
    }
}
